/*
 * 巴法科技:2022-11-22
 * 支持巴法app和小程序配网，长按按键可重新配网
 */
#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <EEPROM.h>
#include <Ticker.h>
#include <ArduinoJson.h>
#include <AceButton.h>
#include <httpUpdate.h>
#include "SPIFFS.h"

WiFiClient client_bemfa_WiFiClient;
HTTPClient http_bemfa_HTTPClient;
// 巴法云服务器地址默认即可
#define TCP_SERVER_ADDR "bemfa.com"
// 服务器端口，tcp创客云端口8344
#define TCP_SERVER_PORT "8344"
// 最大字节数
#define MAX_PACKETSIZE 512
// 设置心跳值30s
#define KEEPALIVEATIME 2 * 1000
//*****可以修改的地方******//
String UID = "";
String TOPIC = "";
String aptype = "006";    // 001插座类型，002灯类型，003风扇类型，004传感器，005空调，006开关，009窗帘
String Name = "五菱荣光"; // 设备昵称，可随意修改
String verSion = "3.1";   // 3.1是tcp协议,1.1是mqtt协议,
String adminID = "0";     // 默认空即可。企业id,建议企业用户配置，该设备会自动绑定到该企业下，获取id方法见接入文档5.17节
int failCount = 10;       // 定义失败连接次数

// tcp客户端相关初始化，默认即可
String TcpClient_Buff = "";
unsigned int TcpClient_BuffIndex = 0;
unsigned long TcpClient_preTick = 0;
unsigned long preHeartTick = 0;    // 心跳
unsigned long preTCPStartTick = 0; // 连接
bool preTCPConnected = false;
bool updateStart = false;

void saveConfig();
void loadConfig();
void doWiFiTick();
void doSmartconfig();
void startTCPClient();
void doTCPClientTick();
void sendtoTCPServer(String p);
void getUid(String &UID, String &TOPIC);

void restoreFactory();
void checkFirstConfig();
void delayRestart(float t);
bool Lanya();
// void taskdoHttpStick();
void Lock();
void Unlock();
void updateBin();
void taskdoHttpStick(String param);
// void doHttpStick(void (*function)(String), String param);
// void doHttpStick(void (*function)(String), String param); // 给列表添加任务函数
// std::vector<void (*)(String)> taskFunctions;
// std::vector<String> TaskParameters;
String device = "五菱荣光"; // 设备名称
String type = "1";          // 1表示是预警消息，2表示设备提醒消息
bool xMutex = false;        // 互斥量
/**
 * 结构体，用于存储配网信息
 */
struct config_type
{
  char stassid[32];
  char stapsw[64];
  char cuid[40];
  char ctopic[32];
  uint8_t magic;
  uint8_t reboot;
};
WiFiUDP Udp;
Ticker delayTimer;
config_type config;
hw_timer_t *tim = NULL; // 声明看门狗指针变量

// 按钮配置接口
// using namespace ace_button;
// AceButton Button(button_Pin);
// String getMinFreeHeap = "";
// 校准时间参数
const char *ntpServer = "ntp2.aliyun.com";
const long gmtOffset_sec = 8 * 3600;
const int daylightOffset_sec = 0;
tm timeinfo;

// 定时器参数
Ticker timerTick; // 创建 Ticker 对象
bool dsq = false; // Ticker新增标志位
bool genxin = false;

String cqyy = "";

int f5;

#include "wlrg.h"
#include "ble.h"

// 升级超时中断函数
void tim1Interrupt()
{
  sendtoTCPServer("cmd=2&uid=" + UID + "&topic=" + TOPIC + "&msg=升级超时系统重启\r\n"); // 推送消息
  delay(1000);
  esp_restart();
}

// 当升级中，打印日志
void update_progress(int cur, int total)
{
  timerWrite(tim, 0); // 喂狗操作，计时器清零
  static bool update = false;
  int bfb = map(cur, 0, total, 0, 100);
  if (bfb % 5 == 0)
  {
    if (!update)
    {
      if (bfb == 100)
      {
        delay(1000);
      }
      client_bemfa_WiFiClient.print("cmd=2&uid=" + UID + "&topic=" + TOPIC + "&msg=" + TOPIC + "#" + String(bfb) + " % \r\n");
      update = true;
      Serial.printf("CALLBACK:  HTTP update process at %d of %d bytes... %d %\n", cur, total, bfb);
    }
  }
  else
  {
    update = false;
  }
}

// 当升级失败时，打印日志
void update_error(int err)
{
  Serial.printf("CALLBACK:  HTTP update fatal error code %d\n", err);
}

// 固件升级函数
void updateBin()
{
  config.reboot = 0;
  EEPROM.begin(256);
  uint8_t *p = (uint8_t *)(&config);
  for (int i = 0; i < sizeof(config); i++)
  {
    EEPROM.write(i, *(p + i));
  }
  EEPROM.commit();

  updateStart = true;
  String upUrl = "http://bin.bemfa.com/b/3BcNDg5MmJkN2ZlMDA1ZWNiYmNjZjM1OTI5MTU3ZWM3ZTc=" + TOPIC + ".bin"; // OTA固件链接，请替换为自己的固件链接，如果接收到msg=update，开始执行固件升级
  // 初始化看门狗定时器
  tim = timerBegin(1, 80, true);
  timerAttachInterrupt(tim, tim1Interrupt, true);
  timerAlarmWrite(tim, 10000 * 1000, true); // 30s触发
  timerAlarmEnable(tim);

  WiFiClient UpdateClient;
  httpUpdate.onProgress(update_progress); // 当升级中
  httpUpdate.onError(update_error);       // 当升级失败时
  t_httpUpdate_return ret = httpUpdate.update(UpdateClient, upUrl);
  switch (ret)
  {
  case HTTP_UPDATE_FAILED: // 当升级失败
    // Serial.println("[update] Update failed.");
    break;
  case HTTP_UPDATE_NO_UPDATES: // 当无升级
    // Serial.println("[update] Update no Update.");
    break;
  case HTTP_UPDATE_OK: // 当升级成功
    // Serial.println("[update] Update ok.");
    break;
  }
}

// 存储配网信息
void saveConfig()
{
  EEPROM.begin(256);
  uint8_t *p = (uint8_t *)(&config);
  for (int i = 0; i < sizeof(config); i++)
  {
    EEPROM.write(i, *(p + i));
  }
  EEPROM.commit();
}

// 重启函数
void delayRestart(float t)
{
  delayTimer.attach(t, []()
                    { ESP.restart(); });
}

// 加载存储的信息，并检查是否恢复出厂信息
void loadConfig()
{
  EEPROM.begin(256);
  uint8_t *p = (uint8_t *)(&config);
  for (int i = 0; i < sizeof(config); i++)
  {
    *(p + i) = EEPROM.read(i);
  }
  config.reboot = config.reboot + 1;
  EEPROM.begin(256);
  for (int i = 0; i < sizeof(config); i++)
  {
    EEPROM.write(i, *(p + i));
  }
  EEPROM.commit();
}

// 检查是否需要配网
void waitKey()
{
  if (strcmp(config.cuid, "4892bd7fe005ecbbccf35929157ec7e7") != 0)
  {
    doSmartconfig();
    getUid(UID, TOPIC);
  }
  else
  {
    UID = config.cuid;     // 赋值UID
    TOPIC = config.ctopic; // 赋值TOPIC
    // Serial.print("stassid:");
    // Serial.println(config.stassid);
    // Serial.print("stapsw:");
    // Serial.println(config.stapsw);
    // Serial.print("cuid:");
    // Serial.println(config.cuid);
    // Serial.print("ctopic:");
    // Serial.println(config.ctopic);
    // Serial.print("magic:");
    // Serial.println(config.magic);
  }
}

// 恢复出厂设置，清除存储的wifi信息
void restoreFactory()
{
  Serial.println("Restore Factory....... ");
  config.magic = 0x00;
  strcpy(config.stassid, "");
  strcpy(config.stapsw, "");
  strcpy(config.cuid, "");
  strcpy(config.ctopic, "");
  saveConfig();
}

// 第一次配网,并创建主题
void getUid(String &UID, String &TOPIC)
{
  // Serial.print("getUid...");
  HTTPClient http_bemfa;
  String mac = WiFi.macAddress().substring(8); // 取mac地址做主题用
  mac.replace(":", "");                        // 去掉:号
  String topic = mac + aptype;                 // 主题添加类型
  http_bemfa.begin(client_bemfa_WiFiClient, "http://api.bemfa.com/api/device/v1/airkiss/?topic=" + topic + "&version=" + verSion + "&ad=" + adminID);
  int httpCode = http_bemfa.GET();
  if (httpCode > 0)
  {
    String payload = http_bemfa.getString();
    // json数据解析
    StaticJsonDocument<200> doc;
    DeserializationError error = deserializeJson(doc, payload);
    if (error)
    {
      // Serial.print(F("de//SerializeJson() failed: "));
      // Serial.println(error.c_str());
    }
    String code = doc["code"];
    if (code == "5723200")
    {
      String docUID = doc["uid"];
      UID = docUID;
      TOPIC = topic;
      strcpy(config.cuid, docUID.c_str());
      strcpy(config.ctopic, topic.c_str());
      saveConfig();
      // Serial.println("ok");
      // Serial.println("UID:" + docUID + "\nTOPIC:" + topic);
    }
    else
    {
      // Serial.println(" config ERROR.........");
    }
    http_bemfa.end();
  }
}

// 第一次配网,保存WIFI配置信息
void doSmartconfig()
{
  bool Smartconfig = false;
  WiFi.disconnect();
  WiFi.mode(WIFI_STA);
  WiFi.stopSmartConfig();
  WiFi.beginSmartConfig();
  Serial.print("Smartconfig begin,Waiting for WeChat Config.....");
  while (!Smartconfig)
  {
    if (WiFi.smartConfigDone())
    {
      Serial.println("Smartconfig ok");
      strcpy(config.stassid, WiFi.SSID().c_str());
      strcpy(config.stapsw, WiFi.psk().c_str());
      WiFi.disconnect();                         // 断开连接
      WiFi.mode(WIFI_STA);                       // STA模式
      WiFi.begin(config.stassid, config.stapsw); // 连接路由器
      Serial.print("wifi...");
      while (WiFi.status() != WL_CONNECTED)
      {
        delay(100);
      }
      Smartconfig = true;
      Serial.println("ok");
    }
  }
}

// 发送数据到TCP服务器
void sendtoTCPServer(String p)
{
  static String startp = "";
  if (!client_bemfa_WiFiClient.connected())
  {
    Serial.println("Client is not readly");
    return;
  }
  if (p == "keep")
  {
    client_bemfa_WiFiClient.print("cmd=0&msg=keep\r\n");
  }
  else
  {
    p += " \r遥" + String(yklock1 ? "on" : "off") + "  锁" + String(digitalRead(doorLockStatusPin) ? "off" : "on") + "  门" + String(digitalRead(doorOpenCloseStatusPin) ? "off" : "on") + "  电" + String(digitalRead(ACCOUT_pin) ? "on" : "off");
    p += "\r" + cqyy + "复位 " + String(config.reboot) + "次  可用内存 " + String(ESP.getMinFreeHeap()) + "字节";
    // p += " 时间:" + String(timeinfo.tm_hour) + ":" + String(timeinfo.tm_min) + ":" + String(timeinfo.tm_sec);
    if (p != startp)
    {
      startp = p;
      client_bemfa_WiFiClient.print("cmd=2&uid=" + UID + "&topic=" + TOPIC + "&msg=" + p + "\r\n");
    }
  }
  preHeartTick = millis(); // 心跳计时开始，需要每隔60秒发送一次数据
}

// 初始化和服务器建立连接
void startTCPClient()
{
  if (client_bemfa_WiFiClient.connect(TCP_SERVER_ADDR, atoi(TCP_SERVER_PORT)))
  {
    String tcpTemp = "cmd=1&uid=" + UID + "&topic=" + TOPIC + "\r\n"; // 构建订阅指令
    Serial.print(tcpTemp);
    Serial.println("bemfa.com config ok");
    Serial.printf("%s: %d\r\n", TCP_SERVER_ADDR, atoi(TCP_SERVER_PORT));
    client_bemfa_WiFiClient.print(tcpTemp); // 发送订阅指令
    client_bemfa_WiFiClient.setNoDelay(true);
  }
  preTCPStartTick = millis();
}
int getMinFreeHeap = 0;
// 检查数据，发送心跳
void doTCPClientTick()
{
  // 检查是否断开，断开后重连
  if (WiFi.status() != WL_CONNECTED)
  {
    return;
  }
  if (!preTCPConnected)
  {
    if (millis() - preTCPStartTick > 30000) // 重新连接
    {
      Serial.print("服务器断开后重连");
      client_bemfa_WiFiClient.stop();
      startTCPClient();
    }
  }
  if (client_bemfa_WiFiClient.connected())
  {
    if (client_bemfa_WiFiClient.available())
    { // 收数据
      char c = client_bemfa_WiFiClient.read();
      TcpClient_Buff += c;
      TcpClient_BuffIndex++;
      TcpClient_preTick = millis();

      if (TcpClient_BuffIndex >= MAX_PACKETSIZE - 1)
      {
        TcpClient_BuffIndex = MAX_PACKETSIZE - 2;
        TcpClient_preTick = TcpClient_preTick - 200;
      }
    }
    if (millis() - preHeartTick >= KEEPALIVEATIME)
    { // 保持心跳
      sendtoTCPServer("keep");
      getLocalTime(&timeinfo);
      Serial.println("保持心跳");
      preTCPConnected = false;
      if (getMinFreeHeap != ESP.getMinFreeHeap())
      {
        getMinFreeHeap = ESP.getMinFreeHeap();
        taskdoHttpStick(yklock1 ? "有钥匙" : "无钥匙");
        sendtoTCPServer(yklock1 ? "有钥匙" : "无钥匙");
      }
      f5 = 0;
    }
  }
  if ((TcpClient_Buff.length() >= 10) && (millis() - TcpClient_preTick >= 10))
  {
    Serial.println("数据:" + TcpClient_Buff);
    preTCPConnected = true;
    preTCPStartTick = millis();
    client_bemfa_WiFiClient.flush();

    if (TcpClient_Buff.indexOf("update") > 0)
    {
      Serial.println("[update] Update Start......");
      updateBin();
    }
    else if (TcpClient_Buff.indexOf("Reset") > 0)
    {
      restoreFactory();
    }
    else if (TcpClient_Buff.indexOf("Root") > 0)
    {
      delayRestart(0);
    }
    else if (TcpClient_Buff.indexOf("Lock") > 0)
    {
      Lock();
    }
    else if (TcpClient_Buff.indexOf("Unlock") > 0)
    {
      Unlock();
    }

    TcpClient_Buff = "";
    TcpClient_BuffIndex = 0;
  }
}
// 检查WiFi是否连接上，若连接成功启动TCP Client
void doWiFiTick()
{
  static bool startSTAFlag = false;
  static bool taskStarted = false;
  static long lastWiFiCheckTick = -10000;
  // 未连接10s重连

  if (WiFi.status() != WL_CONNECTED)
  {
    if (millis() - lastWiFiCheckTick > 10000)
    {
      if (!startSTAFlag)
      {
        startSTAFlag = true;
        WiFi.disconnect();                      // 断开连接
        WiFi.mode(WIFI_STA);                    // STA模式
        WiFi.begin("SenSen_s_car", "15367660"); // 连接路由器
        Serial.println("wifi...1");
        lastWiFiCheckTick = millis();
      }
      else
      {
        startSTAFlag = false;
        WiFi.disconnect();                         // 断开连接
        WiFi.mode(WIFI_STA);                       // STA模式
        WiFi.begin(config.stassid, config.stapsw); // 连接路由器
        Serial.println("wifi...2");
        lastWiFiCheckTick = millis();
      }
    }
    taskStarted = false;
  }
  // 连接成功建立
  else
  {
    if (taskStarted == false)
    {
      Serial.println("wifi config ok");
      Serial.print("IP: ");
      Serial.println(WiFi.localIP());
      startTCPClient();
      taskStarted = true;
    }
  }
}

void taskdoHttpStick(String p) // 任务函数
{
  WiFiClient WiFi;
  HTTPClient http;
  p += "\r" + cqyy + "复位 " + String(config.reboot) + "次\r可用内存 " + String(ESP.getMinFreeHeap()) + "字节";
  String ApiUrl = "http://api.bemfa.com/api/wechat/v1/"; // 默认 api 网址
  String postData = "uid=" + UID + "&type=" + type + "&device=" + device + "&msg=" + p;
  http.begin(WiFi, ApiUrl);                                            // Specify request destination
  http.addHeader("Content-Type", "application/x-www-form-urlencoded"); // Specify content-type header
  http.POST(postData);                                                 // Send the request
  http.end();
  WiFi.stop();
  p = "";
  ApiUrl = "";
  postData = "";
}

const char *getResetReasonText(esp_reset_reason_t reason)
{
  switch (reason)
  {
  case ESP_RST_UNKNOWN:
    return "未知";
  case ESP_RST_POWERON:
    return "上电";
  case ESP_RST_EXT:
    return "外部";
  case ESP_RST_SW:
    return "软件";
  case ESP_RST_PANIC:
    return "异常";
  case ESP_RST_INT_WDT:
    return "中断";
  case ESP_RST_TASK_WDT:
    return "任务";
  case ESP_RST_WDT:
    return "看门";
  case ESP_RST_DEEPSLEEP:
    return "深度";
  case ESP_RST_BROWNOUT:
    return "欠压";
  case ESP_RST_SDIO:
    return "SDIO";
  default:
    return "无效";
  }
}

// 初始化，相当于main 函数
void setup()
{
  Serial.begin(115200);
  Serial.println("Beginning...");
  esp_reset_reason_t reason = esp_reset_reason();
  cqyy = getResetReasonText(reason);

  loadConfig();

  setup_wlrg();
  setup_ble();

  waitKey();
  doWiFiTick();
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
}
// 循环

void loop()
{
  delay(10); // 适当延时，避免线程空转占用过多资源，每次循环等待100毫秒（转换为系统节拍数）
  loop_wlrg();
  doWiFiTick();      // 检查wifi
  doTCPClientTick(); // tcp消息接收
}
